/*
 * Copyright ©2021 su binglun(9085309@qq.com). All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *---------------------------------------------------------------------
 *-                                                                   -
 *-           Website : http://phoenix.sunny360.cn/                   -
 *-                                                                   -
 *---------------------------------------------------------------------
 */
package cn.sunny360.core.service.impl;

import cn.sunny360.core.common.PhResult;
import cn.sunny360.core.service.GroupService;
import cn.sunny360.core.utils.Container;
import cn.sunny360.core.wrapper.DBWrapper;
import com.sohu.idcenter.IdWorker;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Tuple;
import org.joda.time.DateTime;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class GroupServiceImpl implements GroupService {

    private DBWrapper dbWrapper;
    private IdWorker idWorker;

    public GroupServiceImpl(){
        dbWrapper = Container.getComponent(DBWrapper.class);
        idWorker = Container.getComponent(IdWorker.class);
    }

    @Override
    public GroupService getMemberList(Long groupId, Promise<List<JsonObject>> promise) {
        dbWrapper.findList(
                "SELECT\n" +
                "	`group_id`,\n" +
                "	`user_id`,\n" +
                "	`is_manager`,\n" +
                "	`manager_level`,\n" +
                "	`username`,\n" +
                "	`nick_name`,\n" +
                "	`head_img`,\n" +
                "	`comment` \n" +
                "FROM\n" +
                "	ph_group_members \n" +
                "WHERE\n" +
                "	group_id = ?", Tuple.of(groupId))
                .future()
                .onSuccess(promise::complete)
                .onFailure(promise::fail);
        return this;
    }

    @Override
    public GroupService updateGroupVersion(Long userId, Long groupId, Long version, Promise<PhResult> promise) {
        dbWrapper.update("update ph_user_group " +
                "  set group_version=? " +
                "where user_id=? " +
                "  and group_id = ?", Tuple.of(version, userId, groupId))
                .future()
                .onSuccess(h->{
                    promise.complete(PhResult.ok());
                })
                .onFailure(promise::fail);
        return this;
    }

    @Override
    public GroupService getGroupLastVersion(Long userId, Long groupId, Promise<Long> promise) {
        dbWrapper.findOne("select group_version from ph_user_group where user_id = ? and group_id = ?",
                Tuple.of(userId, groupId))
                .future()
                .onSuccess(o->{
                    if(o.isPresent()) {
                        promise.complete(o.get().getLong("group_version"));
                    } else {
                        promise.complete(0l);
                    }
                }).onFailure(promise::fail);
        return this;
    }

    @Override
    public GroupService getGroupList(Long userId, Promise<List<JsonObject>> promise) {
        dbWrapper.findList("select group_id, group_name, logo from ph_user_group where user_id=?",
                Tuple.of(userId))
                .future()
                .onSuccess(promise::complete)
                .onFailure(promise::fail);
        return this;
    }

    @Override
    public GroupService createGroup(String groupName, Long createUserId, List<Long> memberList, Promise<PhResult> promise) {
        //生成群id
        long groupId = idWorker.getId();
        Promise<DBWrapper.SQLTuple> addGroupPromise = addGroup(groupId, groupName, createUserId, memberList.size());
        Promise<List<DBWrapper.SQLTuple>> addGroupMembersPromise = addGroupMembers(groupId, createUserId, memberList);
        Promise<List<DBWrapper.SQLTuple>> addUserGroupPromise = addUsersGroup(groupId, groupName, memberList);
        CompositeFuture.all(addGroupPromise.future(),
                addGroupMembersPromise.future(),
                addUserGroupPromise.future())
                        .onFailure(promise::fail)
                        .onSuccess(f->{
                            DBWrapper.SQLTuple addGroupTuple = f.resultAt(0);
                            List<DBWrapper.SQLTuple> addGroupMembersTuple = f.resultAt(1);
                            List<DBWrapper.SQLTuple> addUserGroupTuple = f.resultAt(2);
                            List<DBWrapper.SQLTuple> tupleList = new ArrayList<>();
                            tupleList.add(addGroupTuple);
                            tupleList.addAll(addGroupMembersTuple);
                            tupleList.addAll(addUserGroupTuple);
                            dbWrapper.executeWithTrans(tupleList)
                                    .future()
                                    .onFailure(promise::fail)
                                    .onSuccess(h->promise.complete(PhResult.ok()));
                        });
        return this;
    }

    private Promise<DBWrapper.SQLTuple> addGroup(Long groupId, String groupName, Long createUserId, int memberCount) {
        Promise<DBWrapper.SQLTuple> result = Promise.promise();
        DBWrapper.SQLTuple tuple = new DBWrapper.SQLTuple(
                "insert into ph_group(\n" +
                "  group_id,\n" +
                "  group_name,\n" +
                "  logo,\n" +
                "  back_image,\n" +
                "  level,\n" +
                "  member_num,\n" +
                "  member_change,\n" +
                "  create_time,\n" +
                "  create_user,\n" +
                "  status,\n" +
                "  verify_type\n" +
                ") values (\n" +
                "  ?,?,?,?,?,?,?,?,?,?,?\n" +
                ")",
                Tuple.of(groupId,
                        groupName,
                        null,
                        null,
                        0,
                        memberCount,
                        memberCount,
                        new DateTime().toString("yyyy-MM-dd HH:mm:ss"),
                        createUserId,
                        1,
                        0
                        ));
        result.complete(tuple);
        return result;
    }

    private Promise<List<DBWrapper.SQLTuple>> addGroupMembers(long groupId, long createUserId, List<Long> memberList) {
        Promise<List<DBWrapper.SQLTuple>> result = Promise.promise();
        StringBuilder sb = new StringBuilder()
                .append("select * from ph_user where user_id in (");
        for (int i = 0; i < memberList.size(); i++) {
            sb.append(memberList.get(i));
            if (i < memberList.size() - 1) {
                sb.append(",");
            }
        }
        sb.append(")");
        dbWrapper.findList(sb.toString(), Tuple.tuple())
                .future()
                .onSuccess(userList->{
                    List<DBWrapper.SQLTuple> sqlTuples = userList.stream()
                            .map(u -> new DBWrapper.SQLTuple(
                                    "insert into ph_group_members(\n" +
                                            "  group_id,\n" +
                                            "  user_id,\n" +
                                            "  is_manager,\n" +
                                            "  manager_level,\n" +
                                            "  username,\n" +
                                            "  nick_name,\n" +
                                            "  head_img,\n" +
                                            "  comment\n" +
                                            ") values (\n" +
                                            "  ?,?,?,?,\n" +
                                            "  ?,?,?,?\n" +
                                            ")",
                                    Tuple.of(
                                            groupId,
                                            u.getLong("user_id"),
                                            u.getLong("user_id") == createUserId ? 1 : 0,
                                            100,
                                            u.getString("username"),
                                            u.getString("nick_name"),
                                            u.getLong("head_img"),
                                            null
                                    ))
                            ).collect(Collectors.toList());
                    result.complete(sqlTuples);
                });
        return result;
    }

    private Promise<List<DBWrapper.SQLTuple>> addUsersGroup(Long groupId, String groupName, List<Long> memberList) {
        Promise<List<DBWrapper.SQLTuple>> result = Promise.promise();
        List<DBWrapper.SQLTuple> sqlTuples = memberList.stream()
                .map(userId -> new DBWrapper.SQLTuple(
                                "insert into ph_user_group(\n" +
                                        "  user_id,\n" +
                                        "  group_id,\n" +
                                        "  group_name,\n" +
                                        "  logo,\n" +
                                        "  group_version\n" +
                                        ") values (\n" +
                                        "  ?,?,?,?,?\n" +
                                        ")", Tuple.of(
                                userId,
                                groupId,
                                groupName,
                                null,
                                0
                        ))
                ).collect(Collectors.toList());
        result.complete(sqlTuples);
        return result;
    }

    @Override
    public GroupService applyJoinGroup(Long groupId, Long applyUserId, Long recommendUserId, String comment, Promise<PhResult> promise) {
        Long applyId = idWorker.getId();
        dbWrapper.findList("select * from ph_user where user_id in (?,?)"
                , Tuple.of(applyUserId, recommendUserId))
                .future()
                .onFailure(promise::fail)
                .onSuccess(list->{
                    JsonObject jsonObject = new JsonObject();
                    list.stream()
                            .forEach(u->{
                                if (u.getLong("user_id").equals(applyUserId)) {
                                    jsonObject.put("user_id", applyUserId)
                                            .put("username", u.getString("username"))
                                            .put("user_nike", u.getString("nick_name"));
                                } else if (u.getLong("user_id").equals(recommendUserId)) {
                                    jsonObject.put("reco_id", recommendUserId)
                                            .put("reco_name", u.getString("username"))
                                            .put("reco_nike", u.getString("nick_name"));
                                }
                            });
                    dbWrapper.insert(
                            "insert into ph_group_apply(\n" +
                            "  apply_id,\n" +
                            "  group_id,\n" +
                            "  user_id,\n" +
                            "  username,\n" +
                            "  user_nike,\n" +
                            "  reco_id,\n" +
                            "  reco_name,\n" +
                            "  reco_nike,\n" +
                            "  comment,\n" +
                            "  feed_status,\n" +
                            "  feed_comment\n" +
                            ") values(\n" +
                            "  ?,?,?,?,?,?,?,?,?,?,?\n" +
                            ")", Tuple.of(
                                    applyId,
                                    groupId,
                                    jsonObject.getLong("user_id"),
                                    jsonObject.getLong("username"),
                                    jsonObject.getLong("user_nike"),
                                    jsonObject.getLong("reco_id"),
                                    jsonObject.getLong("reco_name"),
                                    jsonObject.getLong("reco_nike"),
                                    comment,
                                    0,
                                    null
                            ))
                            .future()
                            .onFailure(promise::fail)
                            .onSuccess(h->promise.complete(PhResult.ok("OK", applyId.toString())));
                });
        return this;
    }

    @Override
    public GroupService addGroupMember(Long groupId, Long userId, String comment, Promise<PhResult> promise) {
        Promise<DBWrapper.SQLTuple> promise1 = addGroupMemberTuple(groupId, userId, comment);
        Promise<DBWrapper.SQLTuple> promise2 = addUserGroupTuple(groupId, userId);
        CompositeFuture.all(promise1.future(), promise2.future())
                .onFailure(promise::fail)
                .onSuccess(h->{
                    dbWrapper.executeWithTrans(h.list())
                            .future()
                            .onFailure(promise::fail)
                            .onSuccess(r->promise.complete(PhResult.ok()));
                });
        return this;
    }

    private Promise<DBWrapper.SQLTuple> addGroupMemberTuple(long groupId, long memberId, String comment) {
        Promise<DBWrapper.SQLTuple> result = Promise.promise();
        dbWrapper.findOne("select * from ph_user where user_id =?", Tuple.of(memberId))
                .future()
                .onSuccess(o->{
                    JsonObject u = o.get();
                    DBWrapper.SQLTuple sqlTuple = new DBWrapper.SQLTuple(
                                    "insert into ph_group_members(\n" +
                                            "  group_id,\n" +
                                            "  user_id,\n" +
                                            "  is_manager,\n" +
                                            "  manager_level,\n" +
                                            "  username,\n" +
                                            "  nick_name,\n" +
                                            "  head_img,\n" +
                                            "  comment\n" +
                                            ") values (\n" +
                                            "  ?,?,?,?,\n" +
                                            "  ?,?,?,?\n" +
                                            ")",
                                    Tuple.of(
                                            groupId,
                                            u.getLong("user_id"),
                                            0,
                                            100,
                                            u.getString("username"),
                                            u.getString("nick_name"),
                                            u.getLong("head_img"),
                                            comment
                                    ));
                    result.complete(sqlTuple);
                });
        return result;
    }

    private Promise<DBWrapper.SQLTuple> addUserGroupTuple(Long groupId, Long userId) {
        Promise<DBWrapper.SQLTuple> result = Promise.promise();
        dbWrapper.findOne(
                "SELECT\n" +
                    "  a.*,\n" +
                    "  IFNULL(b.version,0) version \n" +
                    "FROM\n" +
                    "  ph_group a\n" +
                    "  left JOIN ( SELECT msg_group, max( message_id ) version FROM ph_message ) b ON a.group_id = b.msg_group \n" +
                    "WHERE\n" +
                    "  a.group_id = ?", Tuple.of(groupId))
                .future()
                .onFailure(result::fail)
                .onSuccess(o->{
                    if (!o.isPresent()) {
                        result.fail("group id ["+groupId+"] is not exists.");
                        return;
                    }
                    JsonObject group = o.get();
                    DBWrapper.SQLTuple sqlTuple = new DBWrapper.SQLTuple(
                            "insert into ph_user_group(\n" +
                                    "  user_id,\n" +
                                    "  group_id,\n" +
                                    "  group_name,\n" +
                                    "  logo,\n" +
                                    "  group_version\n" +
                                    ") values (\n" +
                                    "  ?,?,?,?,?\n" +
                                    ")", Tuple.of(
                            userId,
                            groupId,
                            group.getString("group_name"),
                            group.getLong("logo"),
                            group.getLong("version")
                    ));
                    result.complete(sqlTuple);
                });

        return result;
    }

    @Override
    public GroupService approvedApply(Long applyId, Integer feedStatus, String feedComment, Promise<PhResult> promise) {
        //申请审批通过
        if (feedStatus == 1) {
            promise = approApply(applyId, feedStatus, feedComment);
        } else {
            promise = rejectApply(applyId, feedStatus, feedComment);
        }
        return this;
    }

    private Promise<PhResult> rejectApply(Long applyId, Integer feedStatus, String feedComment) {
        Promise<PhResult> resultPromise = Promise.promise();
        dbWrapper.update("update ph_group_apply set feed_status=?,feed_comment=? where apply_id = ?",
                Tuple.of(
                        feedStatus,
                        feedComment,
                        applyId
                )).future()
                .onFailure(resultPromise::fail)
                .onSuccess(h->resultPromise.complete(PhResult.ok()));
        return resultPromise;
    }

    private Promise<PhResult> approApply(Long applyId, Integer feedStatus, String feedComment) {
        Promise<PhResult> resultPromise = Promise.promise();
        dbWrapper.findOne("select * from ph_group_apply where apply_id = ?", Tuple.of(applyId))
                .future()
                .onFailure(resultPromise::fail)
                .onSuccess(o->{
                    JsonObject applyInfo = o.get();
                    Promise<DBWrapper.SQLTuple> promise1 = addGroupMemberTuple(applyInfo.getLong("group_id"),
                            applyInfo.getLong("user_id"), feedComment);
                    Promise<DBWrapper.SQLTuple> promise2 = addUserGroupTuple(applyInfo.getLong("group_id"), applyInfo.getLong("user_id"));
                    CompositeFuture.all(promise1.future(), promise2.future())
                            .onFailure(resultPromise::fail)
                            .onSuccess(f->{
                                dbWrapper.executeWithTrans(
                                        new DBWrapper.SQLTuple("update ph_group_apply set feed_status=?,feed_comment=? where apply_id = ?",
                                                Tuple.of(feedStatus, feedComment, applyId )),
                                        f.resultAt(0),
                                        f.resultAt(1)
                                );
                            });
                });

        return resultPromise;
    }

    @Override
    public GroupService getApplyList(Long groupId, Promise<List<JsonObject>> promise) {
        dbWrapper.findList("select * from ph_group_apply where group_id = ? and feed_status=0",
                Tuple.of(groupId))
                .future()
                .onFailure(promise::fail)
                .onSuccess(promise::complete);
        return this;
    }

    @Override
    public GroupService getOnlineMemberList(Long groupId, Promise<List<JsonObject>> promise) {
        dbWrapper.findList("SELECT\n" +
                "	a.*,\n" +
                "	b.token \n" +
                "FROM\n" +
                "	ph_group_members a\n" +
                "	JOIN ph_online b ON a.user_id = b.user_id \n" +
                "WHERE\n" +
                "	a.group_id = ?", Tuple.of(groupId))
                .future()
                .onSuccess(promise::complete)
                .onFailure(promise::fail);
        return this;
    }
}
